/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.tuples;

import java.util.Iterator;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.googlecode.funcj.Function2;
import com.googlecode.funcj.Reduce;
import com.googlecode.funcj.functions.IntegerFunctions;

/**
 * Helper functions that deal with {@code Pair} objects.
 *
 * @see Pair
 *
 * @author Oliver Horn
 */
public final class Pairs {

	private Pairs() {
	}

	/**
	 * Creates a pair from the given elements.
	 * @return the created pair.
	 */
	public static <A, B> Pair<A, B> newPair(A first, B second) {
		return new Pair<A, B>(first, second);
	}

	/**
	 * Transforms a pair by applying a {@code Function2} to its elements.
	 * @return the result of the transformation
	 */
	public static <A, B, C> C transform(Pair<A, B> pair, Function2<? super A, ? super B,C> function) {
		return function.apply(pair.first(), pair.second());
	}

	/**
	 * Returns an iterable that creates a {@code Pair} from each corresponding pair of two
	 * given input iterables.
	 * If one iterable contains more elements than the other, the excess elements are
	 * discarded.
	 *
	 * <p>The returned iterable's iterator supports {@code remove()} if both given
	 * input iterators do.
	 */
	public static <A, B> Iterable<Pair<A, B>> zip(Iterable<A> firsts, Iterable<B> seconds) {
		return zipWith(firsts, seconds, Pairs.<A, B>makePairFunction2());
	}

    public static <A, B> Pair<Iterable<A>, Iterable<B>> unzip(Iterable<Pair<A, B>> pairs) {
        Iterable<A> firsts = Iterables.transform(pairs, Pairs.<A, B>pairFirstFunction());
        Iterable<B> seconds = Iterables.transform(pairs, Pairs.<A, B>pairSecondFunction());
        return new Pair<Iterable<A>, Iterable<B>>(firsts, seconds);
    }

	/**
	 * Returns an iterable that applies a {@code Function2} to each corresponding pair of two
	 * given input iterables.
	 * If one iterable contains more elements than the other, the excess elements are
	 * discarded.
	 *
	 * <p>The returned iterable's iterator supports {@code remove()} if both given
	 * input iterators do.
	 */
	public static <A, B, C> Iterable<C> zipWith(final Iterable<A> firsts, final Iterable<B> seconds, final Function2<? super A, ? super B, C> function) {
		return new Iterable<C>() {
			public Iterator<C> iterator() {
				final Iterator<A> firstsIter = firsts.iterator();
				final Iterator<B> secondsIter = seconds.iterator();
				return new Iterator<C>() {
					public boolean hasNext() {
						return firstsIter.hasNext() && secondsIter.hasNext();
					}
					public C next() {
						return function.apply(firstsIter.next(), secondsIter.next());
					}
					public void remove() {
						firstsIter.remove();
						secondsIter.remove();
					}
				};
			}
		};
	}

	public static <T> Iterable<Pair<Integer, T>> enumerate(Iterable<T> iterable) {
		return enumerate(iterable, 0);
	}

	public static <T> Iterable<Pair<Integer, T>> enumerate(Iterable<T> iterable, int start) {
		return Pairs.zip(Reduce.unreduce(IntegerFunctions.succFunction(), start), iterable);
	}

	/**
	 * Returns a 2-ary function that creates a pair from its arguments.
	 * @see #newPair(Object, Object)
	 */
	@SuppressWarnings("unchecked")
	public static <A, B> Function2<A, B, Pair<A, B>> makePairFunction2() {
		return (Function2<A, B, Pair<A, B>>) MakePairFunction2.INSTANCE;
	}

	/**
	 * Returns a function that extracts the first element from its argument which
	 * must be a pair.
	 * @see Pair#first()
	 */
	@SuppressWarnings("unchecked")
	public static <A, B> Function<Pair<A, B>, A> pairFirstFunction() {
		return (Function<Pair<A, B>, A>) PairFirstFunction.INSTANCE;
	}

	/**
	 * Returns a function that extracts the second element from its argument which
	 * must be a pair.
	 * @see Pair#first()
	 */
	@SuppressWarnings("unchecked")
	public static <A, B> Function<Pair<A, B>, B> pairSecondFunction() {
		return (Function<Pair<A, B>, B>) PairSecondFunction.INSTANCE;
	}

	@SuppressWarnings("rawtypes")
	private enum MakePairFunction2 implements Function2 {
		INSTANCE;

		public Object apply(Object a, Object b) {
			return new Pair<Object, Object>(a, b);
		}

		@Override
		public String toString() {
			return "makePair";
		}
	}

	@SuppressWarnings("rawtypes")
	private enum PairFirstFunction implements Function {
		INSTANCE;

		public Object apply(Object o) {
			return ((Pair<?, ?>) o).first();
		}

		@Override
		public String toString() {
			return "pairFirst";
		}
	}


	@SuppressWarnings("rawtypes")
	private enum PairSecondFunction implements Function {
		INSTANCE;

		public Object apply(Object o) {
			return ((Pair<?, ?>) o).second();
		}

		@Override
		public String toString() {
			return "pairSecond";
		}
	}
}
